查看原文
其他

开源Hook框架-epic-实现浅析

十八垧 看雪学院 2019-05-25

epic是weishu大神开源的一个Hook框架,支持ART上的Java方法HOOK。


实现原理:http://weishu.me/2017/11/23/dexposed-on-art/


本文走马观花一下。


epic相当于ART上的Dexposed,所以也是Xposed-Style Method Hook。


从 DexposedBridge.findAndHookMethod 开始跟踪代码:




取出最后一个参数 callback,然后调用 XposedHelpers.findMethodExact 得到想要 Hook的method,最后调用 DexposedBridge.hookMethod 进行Hook。


XposedHelpers.findMethodExact 的实现在之前的笔记中已经看过了,这里不看了。


直接看 DexposedBridge.hookMethod:





所有已经Hook过的 method 及其对应的 callbacks,全部存储在 hookedMethodCallbacks 中,这是一个 HashMap。如果该 method 已经 Hook过,那直接把 callback 回调对象加入到其对应的 callbacks 集合中就可以了。这样在该 method 被调用时,callbacks 集合中所有回调都会被遍历执行。


如果该 method 没有被 Hook 过,那就调用 Epic.hookMethod 进行 Hook。


(这里以 Method 为例,Constructor 的 Hook 大同小异)




先看一下 ArtMethod.of:





ArtMethod.of 是以 Method 对象作为参数,创建一个me.weishu.epic.art.Epic.ArtMethod 对象。


artOrigin. method 保存原始的 Java Method 对象。


artOrigin. address 保存的是原始的 Method 对象在ART中对应的 art::mirror::ArtMethod 对象的地址。


artOrigin. objectAddress 保存的是原始的 Java Method 对象(Java Object)在内存中的地址。


EpicNative.getMethodAddress 和 Unsafe.getObjectAddress 的实现代码先不贴了。getMethodAddress 的实现很简单,getObjectAddress 的实现稍复杂,但也不难理解。这里先跟踪主要代码,忽略旁枝末节。


继续看 Epic.hookMethod(ArtMethod artOrigin):


这个函数的实现有点长,分段贴。




首先创建一个MethodInfo对象,用于保存方法信息。其中methodInfo.method保存了原始的Method对象对应的me.weishu.epic.art.method.ArtMethod对象。


然后将MethodInfo对象保存到originSigs中。originSigs是一个Map对象,key是Method对象对应的art::mirror::ArtMethod对象的地址,value是MethodInfo。




调用setAccessible(true),取消Java方法调用时的访问权限检查。




调用ensureResolved,保证静态方法完成解析。为什么要这么干,已经写在注释里了。




如果要 Hook 的方法还未编译,则调用  ArtMethod.compile 主动进行编译,这么做也是因为 epic 是“dynamic callee-side rewriting”。


ArtMethod.compile 是通过调用 JIT 的 jit_compile_method 来完成方法编译的。


最后,compiled_code 入口点会保存到 originEntry 变量中。




为原 Method 创建一个备份,保存到 Epic.backupMethodsMapping 中。




前面是铺垫,最重要的一步来了,具体看注释。


哪些不同的Java方法会具有相同的 compiled_code 入口点呢?

1)所有ART版本上未被 resolve 的 static 函数

2)Android N 以上的未被编译的所有函数

3)代码逻辑一模一样的函数

4)JNI函数


其中,情况1和2在上面已经处理过了,应该不会遇到了,剩下3和4。


对于JNI函数,因为不会涉及到字节码编译,也没有对应的compiled_code,所以其 compiled_code 入口点会统一设置为 GetQuickGenericJniStub,即 art_quick_generic_jni_trampoline。


继续跟 Trampoline.install,看看是如何安装跳板代码来最终完成Hook的。




这个函数的功能描述已经写到注释里了。核心操作有两点:

1)创建Trampoline(包括“二段跳板”BridgeJump,和CallOrigin)

2)创建和安装“一段跳板”,完成Hook。


简单看一下epic的基本原理图:




epic的Hook机制是“dynamic callee-side rewriting”。具体点说:


1)保证要Hook的method完成compile,也就是运行时要执行其compiled_code。

2)根据要 Hook 的 method 对应的 art::mirror::ArtMethod 找到 compiled_code 入口点。

3)在 compiled_code 的开始位置放置一段很短的跳转代码,称为“一段跳板”,作用是跳转到二段跳板。之所以弄一个一段跳板,是怕二段跳板太长,原方法的compiled_code区域放不下。

4)二段跳板会将一些必要的参数打包,调用Java-Bridge方法,并将打包在一起的参数,通过r3传递给Java-Bridge。

5)Java-Bridge 方法取出传递进来的参数,然后根据r1、r2、r3以及sp(以Thumb2为例,除了r0~r3,剩余的参数会通过sp传递),构造出原方法的参数,最后调用DexposedBridge.handleHookedArtMethod。

6)由 DexposedBridge.handleHookedArtMethod 调用 beforeHookedMethod、原方法和 afterHookedMethod。


二段跳板的创建由 Trampoline.create 方法完成,一段跳板的创建和安装由Trampoline. activate 方法完成。


先看Trampoline.create:




那这里创建的BridgeJump(二段跳板)和CallOrigin是什么样子的呢?分别看一下Trampoline. createTrampoline和shellCode.createCallOrigin方法。


先看Trampoline. createTrampoline:




先调用 Entry.getBridgeMethod 返回一个 Bridge 方法,这个Bridge是一个Java方法。然后调用 shellCode.createBridgeJump 创建 BridgeJump(二段跳板)。


我们先看 shellCode.createBridgeJump(以Thumb2为例)创建的 BridgeJump(二段跳板),然后再去看 Entry.getBridgeMethod 返回的 Bridge 方法。调用 shellCode.createBridgeJump 时传入的各个参数的含义已经写到注释里了。




这里创建的就是上面原理图中的二段跳板代码,详情看注释。重点有两处:


1)art::mirror::ArtMethod 对象地址的比较

2)打包参数,然后跳转到 Java-Bridge ,打包之后的参数通过r3传递。

现在可以去看上面由 Entry.getBridgeMethod 返回的 Bridge 方法了(以32位运行时为例)。




假设 returnType 是 Object.class ,那么返回的 Bridge 方法就是 Entry.referenceBridge :




从前面的二段跳板代码可知,传递给referenceBridge的第3个参数struct是一个结构体指针。


按照之前的规则,依次取出sp、r2、r3和sourceMethod。sourceMethod是原Method对应的art::mirror::ArtMethod对象在内存中的地址。然后根据r1、r2、r3以及self、sp(以Thumb2为例,除了r0~r3,剩余的参数会通过sp传递),构造出原方法的参数。然后根据returnType的不同,分别调用onHookXXX函数。


Entry.constructArguments的实现逻辑不难理解,但是从weishu大神的处理来看,不同情况下的兼容还是最头疼的问题。


还是假设returnType是Object.class,看一下Entry. onHookObject:




其实就是调用了DexposedBridge.handleHookedArtMethod。


DexposedBridge.handleHookedArtMethod的逻辑,熟悉Xposed的人应该都很熟悉。前面的whale笔记也跟踪过代码了,这里就不看了。


beforeHookedMethod、原方法和afterHookedMethod都是在DexposedBridge.handleHookedArtMethod里面调用的。


回到Trampoline.create,再看一下shellCode.createCallOrigin:




创建CallOrigin的逻辑就比较简单了,先是compiled_code的原前8个字节的指令(以Thumb2为例),然后是一条跳转指令,跳转到原Method对应的compiled_code的偏移8个字节的位置,也就是一段跳板代码的后面,去执行原Method的compiled_code中剩余的指令。


最后,再回到Trampoline.install,看一下Trampoline. activate是如何创建和安装一段跳板的。




Trampoline. activate直接调用EpicNative.activateNative。


1)参数jumpToAddress原Method的compiled_code入口点。

2)参数是pc是Trampoline代码的首地址,即:前面创建的一块内存,里面是二段跳板BridgeJump和CallOrigin。

3)最后一个参数是一段跳板代码,由shellCode.createDirectJump(pc)创建。

看一下shellCode.createDirectJump是如何创建一段跳板的(以Thumb2为例):




很简单,就是一条ldr指令,将要跳转的地址(二段跳板的代码地址)赋给pc。


最后,看一下EpicNative.activateNative,这是一个native方法,实现代码如下:




这个函数的实现也很简单。就是将一段跳板的代码拷贝到原Method对应的compiled_code的开始处,类似native函数的InlineHook。和Whale一样,这里在安装一段跳板前也暂停了ART的所有线程,原因已经写在注释里了。另外,在arm平台下,更新完指令要记得cacheflush。


至此,epic的Hook就算完成了。


本篇笔记只是草草的跟踪一下代码,并未将所有的实现细节全部看完。但主干代码和实现原理已经算是清楚了。




- End -




看雪ID:十八垧           

https://bbs.pediy.com/user-738921.htm



本文由看雪论坛 十八垧 原创

转载请注明来自看雪社区



热门图书推荐

 立即购买!




热门文章阅读

1. CVE-2017-13772分析及mips栈溢出利用总结

2. 用 Lua 简单还原 OpCode 顺序

3. API监控+代码乱序的壳




公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com


↙点击下方“阅读原文”

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存